package ph.com.gs3.formalistics.model.valueobjects.business.document;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import ph.com.gs3.formalistics.global.utilities.logging.FLLogger;

public class Document implements Serializable, AbstractDocument {

	private static final long serialVersionUID = -2333158191270036293L;

	public static final String TAG = Document.class.getSimpleName();

	public static enum DocumentStar {
		STARRED, UNSTARRED
	}

	private String dbId;
	private String webId;
	private String server;
	private String formWebId;

	private String trackingNumber;
	private String status;
	private String processorWebId;
	private String requestorWebId;

	private String dateCreated;
	private String dateUpdated;
	private String dateCommentsLastUpdated;

	private String workflowId;
	private String currentNodeId;

	private boolean isStarred;

	// fields does not have raws, it is generated from the document's whole raw
	private JSONObject fields;

	// raws
	private String rawFieldsEnabled;// JSONArray
	private String rawFieldsRequired;// JSONArray
	private String rawFieldsHidden;// JSONArray

	private String rawActions;	// JSONArray

	// parsed raws
	private List<String> fieldsEnabled;
	private List<String> fieldsRequired;
	private List<String> fieldsHidden;

	private String ownerId;
	
	// Automatically set when the raw actions field is set
	private List<DocumentAction> availableActions = new ArrayList<>();

	private JSONObject documentRawJSON;

	// =======================================================================
	// {{ Constants - System Fields

	protected static final String[] systemFields = { "ID", "TrackNo", "Status", "Processor",
	        "Requestor", "LastAction", "DateCreated", "DateUpdated", "CreatedBy", "UpdatedBy",
	        "Unread", "Node_ID", "Workflow_ID", "fieldEnabled", "fieldRequired",
	        "fieldHiddenValues", "imported", "Repeater_Data", "Editor", "Viewer",
	        "middleware_process", "is_starred", "ProcessorType", "ProcessorLevel" };

	protected static final String[] processorSystemFields = { "processor_email",
	        "processor_display_name", "processor_first_name", "processor_last_name",
	        "processor_contact_number", "processor_company_id", "processor_department_id",
	        "processor_user_level_id", "processor_image_url" };

	protected static final String[] requestorSystemFields = { "requestor_email",
	        "requestor_display_name", "requestor_first_name", "requestor_last_name",
	        "requestor_contact_number", "requestor_company_id", "requestor_department_id",
	        "requestor_user_level_id", "requestor_image_url" };

	// }}

	// =======================================================================
	// Builder Methods

	public static Document createNewBlankDocument(String server, String formWebId) {

		Document document = new Document();

		document = new Document();
		document.setWebId("0");
		document.setServer(server);
		document.setFormWebId(formWebId);

		return document;

	}

	public static Document createDocumentFromJSON(JSONObject rawObject, String server,
	        String formWebId) throws JSONException {

		Document document = new Document();

		document.documentRawJSON = rawObject;
		document.server = server;
		document.formWebId = formWebId;

		// @formatter:off
		document = 	document.mapBasicFields()
							.mapRaws()
							.mapActions()
							.mapFieldProperties()
							.mapFields();
		// @formatter:on

		return document;

	}

	public Document mapBasicFields() throws JSONException {

		// @formatter:off
		webId 			= documentRawJSON.getString("ID");
		trackingNumber 	= documentRawJSON.getString("TrackNo");
		status 			= documentRawJSON.getString("Status");
		processorWebId 	= documentRawJSON.getString("Processor");
		requestorWebId 	= documentRawJSON.getString("Requestor");
		
		workflowId		= documentRawJSON.getString("Workflow_ID");
		currentNodeId	= documentRawJSON.getString("Node_ID");
		dateUpdated		= documentRawJSON.getString("DateUpdated");
		dateCreated		= documentRawJSON.getString("DateCreated");		
		// @formatter:on

		String rawStarred = documentRawJSON.getString("is_starred");
		setStarred("1".equals(rawStarred));

		return this;

	}

	public Document mapRaws() throws JSONException {

		rawFieldsEnabled = documentRawJSON.getString("fieldEnabled");
		rawFieldsRequired = documentRawJSON.getString("fieldRequired");
		rawFieldsHidden = documentRawJSON.getString("fieldHiddenValues");

		// rawActions is generated in mapActions together with availableActions

		return this;

	}

	public Document mapActions() throws JSONException {

		String rawLastAction = documentRawJSON.getString("LastAction");

		JSONObject lastAction;
		if (rawLastAction == null || rawLastAction.isEmpty()
		        || "null".equalsIgnoreCase(rawLastAction)) {
			lastAction = new JSONObject();
		} else {
			// rawActions = new JSONObject(rawActions);

			// convert the LastAction to formatted available actions
			lastAction = new JSONObject(rawLastAction);
		}

		// throws JSONException
		JSONArray formattedAvailableActions = createFormattedAvailableActionsFromLastAction(lastAction);
		rawActions = formattedAvailableActions.toString();
		availableActions = createActionsFromJSONArray(formattedAvailableActions);

		return this;

	}

	public Document mapFieldProperties() throws JSONException {

		fieldsEnabled = new ArrayList<String>();
		fieldsRequired = new ArrayList<String>();
		fieldsHidden = new ArrayList<String>();

		if (rawFieldsEnabled != null && !rawFieldsEnabled.isEmpty()
		        && !"null".equalsIgnoreCase(rawFieldsEnabled)) {
			JSONArray fieldsEnabledJSONArray = new JSONArray(rawFieldsEnabled);
			for (int i = 0; i < fieldsEnabledJSONArray.length(); i++) {
				fieldsEnabled.add(fieldsEnabledJSONArray.getString(i));
			}
		}

		if (rawFieldsRequired != null && !rawFieldsRequired.isEmpty()
		        && !"null".equalsIgnoreCase(rawFieldsRequired)) {
			JSONArray fieldsRequiredJSONArray = new JSONArray(rawFieldsRequired);
			for (int i = 0; i < fieldsRequiredJSONArray.length(); i++) {
				fieldsRequired.add(fieldsRequiredJSONArray.getString(i));
			}
		}

		if (rawFieldsHidden != null && !rawFieldsHidden.isEmpty()
		        && !"null".equalsIgnoreCase(rawFieldsHidden)) {
			JSONArray fieldsHiddenJSONArray = new JSONArray(rawFieldsHidden);

			for (int i = 0; i < fieldsHiddenJSONArray.length(); i++) {
				fieldsHidden.add(fieldsHiddenJSONArray.getString(i));
			}
		}

		return this;

	}

	public Document mapFields() throws JSONException {

		fields = Document.extractDataFieldsFromDocument(this);
		return this;
	}

	// =======================================================================
	// {{ Parser & Extractor Methods

	/**
	 * Not yet tested
	 * 
	 * @param document
	 * @return
	 * @throws JSONException
	 */
	public static JSONObject extractDataFieldsFromDocument(Document document) throws JSONException {

		JSONObject data = new JSONObject();
		JSONObject documentFields = document.getDocumentRawJSON();

		List<String> systemFieldsList = Arrays.asList(systemFields);
		List<String> processorFieldsList = Arrays.asList(processorSystemFields);
		List<String> requestorFieldsList = Arrays.asList(requestorSystemFields);

		Iterator<?> keys = documentFields.keys();

		while (keys.hasNext()) {
			String key = (String) keys.next();
			if (!systemFieldsList.contains(key) && !processorFieldsList.contains(key)
			        && !requestorFieldsList.contains(key)) {
				// throws JSONException
				data.put(key, documentFields.get(key));
			}
		}

		return data;

	}

	public static JSONArray createFormattedAvailableActionsFromLastAction(JSONObject lastAction)
	        throws JSONException {

		JSONArray availableActions = new JSONArray();

		if (lastAction == null) {
			return availableActions;
		}

		Iterator<?> iterator = lastAction.keys();

		int keyCount = 0;

		while (iterator.hasNext()) {
			String key = (String) iterator.next();
			keyCount++;
			try {
				JSONObject actionFromLastAction = new JSONObject(lastAction.getString(key));
				JSONObject newAvailableAction = new JSONObject();

				// throws JSONException
				newAvailableAction.put("label", key);
				newAvailableAction.put("action", actionFromLastAction.getString("child_id"));

				availableActions.put(newAvailableAction);
			} catch (JSONException e) {
				e.printStackTrace();
				FLLogger.d(TAG, "Failed to extract actions: " + lastAction.getString(key));
				FLLogger.e(TAG, "Failed to extract actions: " + lastAction.getString(key));
			}

		}

		if (keyCount > 0) {
			// add save and cancel
			JSONObject saveAction = new JSONObject();
			JSONObject cancelAction = new JSONObject();

			saveAction.put("label", "Save");
			saveAction.put("action", "Save");

			cancelAction.put("label", "Cancel");
			cancelAction.put("action", "Cancel");

			availableActions.put(saveAction);
			availableActions.put(cancelAction);

		}

		return availableActions;

	}

	public static List<DocumentAction> createActionsFromJSONArray(JSONArray actionsJSONArray)
	        throws JSONException {

		List<DocumentAction> actions = new ArrayList<DocumentAction>();

		int actionCount = actionsJSONArray.length();
		for (int i = 0; i < actionCount; i++) {
			JSONObject rawAction = actionsJSONArray.getJSONObject(i);
			DocumentAction action = new DocumentAction();

			action.setAction(rawAction.getString("action"));
			action.setLabel(rawAction.getString("label"));

			actions.add(action);

		}

		return actions;
	}

	// }}

	// =======================================================================
	// {{ Getters & Setters

	@Override
	public String toString() {
		return "Document " + webId + " from " + server;
	}

	public String getDbId() {
		return dbId;
	}

	public void setDbId(String dbId) {
		this.dbId = dbId;
	}

	public String getWebId() {
		return webId;
	}

	public void setWebId(String webId) {
		this.webId = webId;
	}

	public String getServer() {
		return server;
	}

	public void setServer(String server) {
		this.server = server;
	}

	public String getFormWebId() {
		return formWebId;
	}

	public void setFormWebId(String formWebId) {
		this.formWebId = formWebId;
	}

	public String getTrackingNumber() {
		return trackingNumber;
	}

	public void setTrackingNumber(String trackingNumber) {
		this.trackingNumber = trackingNumber;
	}

	public String getStatus() {
		return status;
	}

	public void setStatus(String status) {
		this.status = status;
	}

	public String getProcessorWebId() {
		return processorWebId;
	}

	public void setProcessorWebId(String processorWebId) {
		this.processorWebId = processorWebId;
	}

	public String getRequestorWebId() {
		return requestorWebId;
	}

	public void setRequestorWebId(String requestorWebId) {
		this.requestorWebId = requestorWebId;
	}

	public String getWorkflowId() {
		return workflowId;
	}

	public void setWorkflowId(String workflowId) {
		this.workflowId = workflowId;
	}

	public String getCurrentNodeId() {
		return currentNodeId;
	}

	public void setCurrentNodeId(String currentNodeId) {
		this.currentNodeId = currentNodeId;
	}

	public List<String> getFieldsEnabled() {
		return fieldsEnabled;
	}

	public void setFieldsEnabled(List<String> fieldsEnabled) {
		this.fieldsEnabled = fieldsEnabled;
	}

	public List<String> getFieldsRequired() {
		return fieldsRequired;
	}

	public void setFieldsRequired(List<String> fieldsRequired) {
		this.fieldsRequired = fieldsRequired;
	}

	public List<String> getFieldsHidden() {
		return fieldsHidden;
	}

	public void setFieldsHidden(List<String> fieldsHidden) {
		this.fieldsHidden = fieldsHidden;
	}

	public JSONObject getDocumentRawJSON() {
		return documentRawJSON;
	}

	public void setDocumentRawJSON(JSONObject fields) {
		this.documentRawJSON = fields;
	}

	public JSONObject getFields() {
		return fields;
	}

	public void setFields(JSONObject fields) {
		this.fields = fields;
	}

	public String getDateCreated() {
		return dateCreated;
	}

	public void setDateCreated(String dateCreated) {
		this.dateCreated = dateCreated;
	}

	public String getDateCommentsLastUpdated() {
		return dateCommentsLastUpdated;
	}

	public void setDateCommentsLastUpdated(String dateCommentsLastUpdated) {
		this.dateCommentsLastUpdated = dateCommentsLastUpdated;
	}

	public boolean isStarred() {
		return isStarred;
	}

	public void setStarred(boolean isStarred) {
		this.isStarred = isStarred;
	}

	public String getRawFieldsEnabled() {
		return rawFieldsEnabled;
	}

	public void setRawFieldsEnabled(String rawFieldsEnabled) {
		this.rawFieldsEnabled = rawFieldsEnabled;
	}

	public String getRawFieldsRequired() {
		return rawFieldsRequired;
	}

	public void setRawFieldsRequired(String rawFieldsRequired) {
		this.rawFieldsRequired = rawFieldsRequired;
	}

	public String getRawFieldsHidden() {
		return rawFieldsHidden;
	}

	public void setRawFieldsHidden(String rawFieldsHidden) {
		this.rawFieldsHidden = rawFieldsHidden;
	}

	public String getRawActions() {
		return rawActions;
	}

	public void setRawActions(String actions) {
		this.rawActions = actions;
	}

	public List<DocumentAction> getAvailableActions() {
		return availableActions;
	}

	public void setAvailableActions(List<DocumentAction> availableActions) {
		this.availableActions = availableActions;
	}

	public String getDateUpdated() {
		return dateUpdated;
	}

	public void setDateUpdated(String dateUpdated) {
		this.dateUpdated = dateUpdated;
	}

	// }}

	@Override
	public DocumentType getDocumentType() {
		return DocumentType.NORMAL;
	}

	public String getOwnerId() {
	    return ownerId;
    }

	public void setOwnerId(String ownerId) {
	    this.ownerId = ownerId;
    }

}
